#pragma once
#include <iostream>
#include <string>
#include <vector>
#include <unordered_map>
#include <fstream>
#include <mutex>

#include "Log.hpp"
#include "util.hpp"

namespace ns_index{
    struct Docinfo_t{
        std::string title;  
        std::string content;
        std::string url;
        uint64_t doc_id;   //构建正排拉链后会返回一个Docinfo_t的对象，这里保存的文档id是为了填充invertedElem的id
    };

    struct invertedElem{
        std::string word;  //关键词
        uint64_t doc_id;   //对应的文档id
        int weight;        //权重
        invertedElem() : weight(0) {}
    };

    
    typedef std::vector<invertedElem> inverted_list; // 倒排拉链

    class index
    {
    private:
        // 正排索引，直接利用下标作为文档id，索引到文档
        std::vector<Docinfo_t> forward_index;

        // 倒排索引一定是一个关键字和一组(个)InvertedElem对应[关键字和倒排拉链的映射关系]
        std::unordered_map<std::string, inverted_list> inverted_index;

    private:    //私有化，单例模式
        index() {}
        index(const index &) = delete;
        index &operator=(const index &) = delete;

        static index *instance;
        static std::mutex mtx;

    public: 
        //获取单例
        static index *Getinstance(){
            if (nullptr == instance){
                mtx.lock();     //加锁
                if (nullptr == instance){
                    instance = new index();
                }
                mtx.unlock();
            }
            return instance;
        }

        ~index() {}
    public:
        //根据文档id，获取文档内容
        Docinfo_t *GetForwardIndex(uint64_t doc_id){
            if (doc_id >= forward_index.size()){
                LOG(WARNING,"doc_id error");
                return nullptr;
            }
            return &(forward_index[doc_id]);
        }
        
        //根据关键词，获取倒排拉链
        inverted_list *GetInvertedList(const std::string &word){
            auto iter = inverted_index.find(word);
            if (iter == inverted_index.end())
            {
                LOG(INFO,"have no about %s inverted_list",word);
                return nullptr;
            }
            return &(iter->second);
        }

        // 用Parser处理完的数据来建立索引  input:./data/raw_html
        bool BulidIndex(const std::string &input) {
            std::ifstream in(input, std::ios::in | std::ios::binary);
            if (!in.is_open()){
                LOG(WARNING,"open %s failed...",input);
                return false;
            }
            std::string line;
            int cnt = 0;
            while (std::getline(in, line)){
                Docinfo_t *Doc = BulidForwardIndex(line); // 建立正排索引
                if (nullptr == Doc){
                    LOG(WARNING,"%s 正排索引建立失败...",line);
                    continue;
                }
                // 建立倒排索引
                BulidInvertedIndex(*Doc);
                cnt++;
                
                if (cnt % 50 == 0){
                    LOG(INFO,"当前已经建立的索引的文档数：%d",cnt);
                }
            }
            LOG(INFO,"建立正排倒排索引 success...");

            in.close();

            return true;
        }

    public:

        //根据一个html文件的\3 \3 \n 的string，构建一个Docinfo_t对象，放入forward_index中
        Docinfo_t *BulidForwardIndex(const std::string &line){
            std::vector<std::string> result;
            const std::string sep = "\3";
            ns_util::StringUtil::StringCut(line, &result, sep);

            // for debug
            // for (auto &e : result)
            // {
            //     std::cout << e << std::endl;
            // }

            if (result.size() != 3)    return nullptr;

            Docinfo_t Doc;
            Doc.title = result[0];
            Doc.content = result[1];
            Doc.url = result[2];
            Doc.doc_id = forward_index.size();

            forward_index.push_back(std::move(Doc));

            return &forward_index.back();
        }

        //根据建立正排索引返回的Docinfo_t对象建立倒排索引
        void BulidInvertedIndex(const Docinfo_t &Doc){
            //统计词频的结构体  根据词频计算权重，根据权重后面需要排序
            struct word_cnt{
                int title_cnt;
                int content_cnt;
                word_cnt() : title_cnt(0), content_cnt(0) {}
            };

            std::unordered_map<std::string, word_cnt> words_map;

            //对标题进行分词，并且统计词频
            std::vector<std::string> title_words;
            ns_util::JiebaUtil::JiebaStringCut(Doc.title, &title_words);
            for (std::string s : title_words){
                boost::to_lower(s);   //在统计与搜索的过程中，我们都是用小写
                words_map[s].title_cnt++;
            }
           //对内容进行分词，并且统计词频
            std::vector<std::string> content_words;
            ns_util::JiebaUtil::JiebaStringCut(Doc.content, &content_words);
            for (std::string s : content_words){
                boost::to_lower(s);
                words_map[s].content_cnt++;
            }

            // for debug
            // for (auto &words_pair : words_map)
            // {
            //     std::cout << words_pair.first <<"  "<< words_pair.second.content_cnt <<"  "<< words_pair.second.title_cnt << std::endl;
            // }
            //LOG(INFO,"BulidInvertedIndex success");

#define X 10
#define Y 1
            //std::unordered_map<std::string, word_cnt> words_map 
            //words_map里面存放的是该html标题与内容进行分词后，
            //[每一个词对应的在标题与内容中出现的次数(词频结构体)的映射关系]
            for (auto &words_pair : words_map){
                invertedElem elem;
                elem.doc_id = Doc.doc_id;      //这里体现了为什么Docinfo里面需要保存doc_id
                elem.word = words_pair.first;

                //权重的计算
                elem.weight = X * words_pair.second.title_cnt + Y * words_pair.second.content_cnt;

                //根据关键词，利用map []的特性，如果map中存在该词，则返回对应的倒排拉链，
                //如果不存在，则插入，再返回对应的倒排拉链
                inverted_list &invertedlist = inverted_index[words_pair.first];
                
                // // for debug
                // // std::cout<<words_pair.first<<std::endl;
                // std::cout << elem.word << " " << elem.doc_id << " " << elem.weight << std::endl;
                //讲填充好的invertedElem 插入到返回的倒排拉链中
                invertedlist.push_back(std::move(elem));

            }
        }
    };

    index *index::instance = nullptr;
    std::mutex index::mtx;
}
